package com.xuecheng.content.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xuecheng.api.content.model.dto.CourseBaseDTO;
import com.xuecheng.api.content.model.dto.TeachplanDTO;
import com.xuecheng.api.content.model.dto.TeachplanMediaDTO;
import com.xuecheng.api.media.model.dto.MediaDTO;
import com.xuecheng.common.domain.code.CommonErrorCode;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.common.enums.common.AuditEnum;
import com.xuecheng.common.enums.content.CourseAuditEnum;
import com.xuecheng.common.enums.content.CourseModeEnum;
import com.xuecheng.common.enums.content.TeachPlanEnum;
import com.xuecheng.common.exception.ExceptionCast;
import com.xuecheng.common.util.StringUtil;
import com.xuecheng.content.agent.MediaApiAgent;
import com.xuecheng.content.common.constant.ContentErrorCode;
import com.xuecheng.content.convert.TeachplanConvert;
import com.xuecheng.content.convert.TeachplanMediaConvert;
import com.xuecheng.content.entity.CourseBase;
import com.xuecheng.content.entity.Teachplan;
import com.xuecheng.content.entity.TeachplanMedia;
import com.xuecheng.content.entity.ext.TeachplanNode;
import com.xuecheng.content.mapper.TeachplanMapper;
import com.xuecheng.content.service.TeachplanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 课程计划 服务实现类
 * </p>
 *
 * @author itcast
 */
@Slf4j
@Service
class TeachplanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements TeachplanService {
    //创建课程信息service层对象
    @Autowired
    private CourseBaseServiceImpl courseBaseService;
    @Autowired
    private TeachplanMediaServiceImpl teachplanMediaService;
    @Autowired
    private MediaApiAgent mediaApiAgent;

    /*todo 根据课程id查询课程计划
     * 业务分析：
     *  1.判断关键数据：操作时的必要数据（一般是由前端来传递）
     *       courseId  companyId
     *  2.判断业务数据：操作时，判断数据的状态（一般是判断数据值，数据值来源于后端的数据源）
     *     课程基本信息
     *           判断是否是同一家教学机构
     *           判断课程是否删除
     *  3.获得课程下的所有课程计划数据
     *  4 判断课程是否有计划
     *  5 有课程计划在通过java的递归来生成课程计划树形结构
     *  6.将node数据转为dto并返回
     * */
    @Override
    public TeachplanDTO queryTreeNodes(Long courseId, Long companyId) {
        //todo 1.判断关键数据：操作时的必要数据（一般是由前端来传递）
//        if(ObjectUtils.isEmpty(companyId)||ObjectUtils.isEmpty(companyId)){
//            ExceptionCast.cast(CommonErrorCode.E_100101);
//        }
         /* todo 2.判断业务数据：操作时，判断数据的状态（一般是判断数据值，数据值来源于后端的数据源）
                     课程基本信息
                                 判断是否是同一家教学机构
                                 判断课程是否删除
                     创建CourseBaseServiceImpl层的对象调用方法getCourseById
                     该方法 完成了前两部的判断,别并且还封装了
                                 */

        courseBaseService.getCourseById(courseId, companyId);
        //todo 3.获得课程下的所有课程计划数据
        //todo  3.1 创建mapper层对象
        TeachplanMapper baseMapper = this.getBaseMapper();
        List<TeachplanNode> teachplanNodes = baseMapper.selectTreeNodesByCourseId(courseId);
        //创建课程计划对象
        TeachplanDTO teachplanDTO = null;
        //todo 4判断课程是否有计划,没有就返回一个空的课程计划
        if (!(ObjectUtils.isEmpty(teachplanNodes))) {
            //todo  5.通过java的递归来生成课程计划树形结构
            //todo  5.1删除掉集合中的第一条数据,因为是树形结构的根节点对象,因为这样可以提高递归效率
            TeachplanNode root = teachplanNodes.remove(0);
            //todo 5.2将根节点,和所有子节点传入递归方法(generateTreeNodes自定义的方法)
            generateTreeNodes(root, teachplanNodes);
            //todo 6.将node数据转为dto并返回
            teachplanDTO = TeachplanConvert.INSTANCE.entity2dto(root);
        } else {
            //如果课程没有计划直接返回空
            teachplanDTO = new TeachplanDTO();


        }

        return teachplanDTO;
    }


    /**
     * todo 递归方法
     * 查询课程计划中的所有数据并进行递归查询
     *
     * @param parentNode 根据课程id获得的根节点
     * @param nodes      所有的子节点
     * @return TeachplanNode   递归生成的课程计划
     */
    private void generateTreeNodes(TeachplanNode parentNode, List<TeachplanNode> nodes) {
        //todo 1判断根节点中的属性(teachplanNode.getChildrenNodes())是否为空,该属性包含所有的子节点,也就是省份下所有的市
        if (CollectionUtils.isEmpty(parentNode.getChildrenNodes())) {
            //todo 2如果为空创建一个集合
            parentNode.setChildrenNodes(new ArrayList<>());

        }
        //todo 3遍历数据表中所有数据(不管是几级数据,就是所有的省市数据),
        for (TeachplanNode node : nodes) {
            //todo 4判断该数据是不是省下面的市,是就添加进去

            if (ObjectUtils.nullSafeEquals(parentNode.getId(), node.getParentid())) {
                parentNode.getChildrenNodes().add(node);
                    /*todo 5判断是最后一级数据吗,不是就再次调用方法进行递归
                        node.getGrade()   是表中数据的一个属性标注了该数据是第几级数据
                        TeachPlanEnum.THIRD_LEVEL   是定义的枚举类
                 */
                if (!(TeachPlanEnum.THIRD_LEVEL.equals(node.getGrade()))) {
                    //todo 6再次调用递归方法,进行数据分级
                    generateTreeNodes(node, nodes);
                }else {
                    LambdaQueryWrapper<TeachplanMedia> queryWrapper = new LambdaQueryWrapper<>();
                    queryWrapper.eq(TeachplanMedia::getTeachplanId,node.getId());
                    TeachplanMedia teachplanMedia = teachplanMediaService.getOne(queryWrapper);
                    node.setTeachplanMedia(teachplanMedia);
                }
            }
        }
    }

    /*
      todo  课程计划增加和修改
          判断是否需要开启事物
        1判断前端传的数据是否有id
            1.1 无id是增加
                  添加操作(二,三级课程计划),并返回dto数据
            1.2 有id是修改
                   修改操作(二,三级课程计划),并返回dto数据

     */

    @Transactional  //开启事物
    public TeachplanDTO createOrModifyTeachPlan(TeachplanDTO dto, Long companyId) {
        Long teachplanId = dto.getTeachPlanId();
        TeachplanDTO resulDTO = null;
        //todo 1判断前端传的数据是否有id
        if (ObjectUtils.isEmpty(teachplanId)) {

            //todo 为空,说明无id添加
            resulDTO = createTeachPlan(dto, companyId);

        } else {
            //有id修改
            resulDTO = modifyTeachPlan(dto, companyId);
        }
        return resulDTO;
    }


    /*
      todo 课程计划修改
        业务分析:
               1判断关键数据:(前端传入过来必须有的数据)
                  pname,parentid,courseid
               2 判断业务数据
                   课程基本信息
                       判断课程是否是同一家机构
                       判断课程是否删除
                       判断课程审核状态
                           只有审核未通过或未提及交才可以操作
               3 dto转po存入数据库
               4判断修改是否成功
               5po转dto返回给前端
     */
    private TeachplanDTO modifyTeachPlan(TeachplanDTO dto, Long companyId) {
        /*
         todo 1判断关键数据:(前端传入过来必须有的数据)
                  pname,parentid,courseid
         */
        if (StringUtil.isBlank(dto.getPname())) {
            ExceptionCast.cast(ContentErrorCode.E_120413);
        }
        if (ObjectUtils.isEmpty(dto.getParentid())) {
            ExceptionCast.cast(ContentErrorCode.E_120408);
        }
        if (ObjectUtils.isEmpty(dto.getCourseId())) {
            ExceptionCast.cast(ContentErrorCode.E_120013);
        }
/*todo
      2 判断业务数据
                   课程基本信息
                       判断课程是否是同一家机构
                       判断课程是否删除
                       判断课程审核状态
                           只有审核未通过或未提及交才可以操作
 */
        CourseBaseDTO courseBase = getCourseBase(dto, companyId);
        // todo 3 dto转po存入数据库
        Teachplan teachplan = TeachplanConvert.INSTANCE.dto2entity(dto);
        //todo 4判断修改是否成功
        LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Teachplan::getId, teachplan.getId());
        Teachplan one = this.getOne(queryWrapper);
        //todo 4.1判断要修改的课程计划是否存在,不存在没修改的必要了直接报错
        if (ObjectUtils.isEmpty(one)) {
            ExceptionCast.cast(ContentErrorCode.E_120401);
        }
        //todo 4.2添加要修改的字段值
        LambdaUpdateWrapper<Teachplan> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(Teachplan::getPname, dto.getPname());
        updateWrapper.set(Teachplan::getIsPreview, dto.getIsPreview());
        updateWrapper.set(Teachplan::getMediaType, dto.getMediaType());
        updateWrapper.set(Teachplan::getStartTime, dto.getStartTime());
        updateWrapper.set(Teachplan::getEndTime, dto.getEndTime());
        updateWrapper.eq(Teachplan::getId, dto.getTeachPlanId());
        boolean update = this.update(updateWrapper);
        if (!update) {
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }
        Teachplan byId = this.getById(dto.getTeachPlanId());
        //todo 5 po转dto返回给前端
        TeachplanDTO teachplanDTO = TeachplanConvert.INSTANCE.ent2dto(byId);
        return teachplanDTO;
    }

    /*
    todo 课程计划添加
     业务分析:
              1判断关键数据
                      判断课程计划名称
              2判断课程基本信息
                      因为: 课程计划添加需要根据课程信息
                      判断课程基本信息是否是同一家机构
                      判断课程是否删除
                      判断课程审核状态
                        只有审核状态未通过,未提交可以操作
             3 dto转po添加数据
             4获得父级节点数据(新增加的数据没有parentid)
               获得parentid
               获得父级节点的等级,给新添加的数据添加等级
               获得父节点中子节点的个数,给新添加的数据排序
              5 添加课程计划
              6转为dto返回
     */
    private TeachplanDTO createTeachPlan(TeachplanDTO dto, Long companyId) {
        //todo 1判断课程计划名称
        String pname = dto.getPname();
        if (StringUtil.isBlank(pname)) {
            ExceptionCast.cast(ContentErrorCode.E_120413);
        }
        /*
        todo 2判断课程基本信息
                      因为: 课程计划添加需要根据课程信息
                      判断课程基本信息是否是同一家机构
                      判断课程是否删除
                      判断课程审核状态
         */
        CourseBaseDTO courseBase = getCourseBase(dto, companyId);
        //todo 3 dto转po添加数据
        Teachplan teachplan = TeachplanConvert.INSTANCE.dto2entity(dto);
        /*todo 4获得父级节点数据(新增加的数据没有parentid)
                      获得parentid
                      获得父级节点的等级,给新添加的数据添加等级
                      获得父节点中子节点的个数,给新添加的数据排序*/
        Teachplan parentNode = getParentNode(teachplan, courseBase);
        Long parentId = parentNode.getId();
        teachplan.setParentid(parentId);
         teachplan.setGrade(parentNode.getGrade() + 1); //获得父级节点的等级,给新添加的数据添加等级


        //todo 使用sql 中的count方法 ,该方法可以查询出某个条件相同的多个数据,在数据库中的最后一条
        LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Teachplan::getParentid, parentId);
        int count = this.count(queryWrapper);
        teachplan.setOrderby(count + 1);//获得父节点中子节点的个数,给新添加的数据排序
        //todo 5 添加课程计划
        teachplan.setCourseId(courseBase.getCourseBaseId());
        boolean save = this.save(teachplan);
        //todo 5.1 判断是否添加成功
        if (!save) {
            //没有添加进去,课程计划操作失败
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }
        //todo  6 po转为dto返回
        TeachplanDTO teachplanDTO = TeachplanConvert.INSTANCE.ent2dto(teachplan);

        return teachplanDTO;
    }

    private CourseBaseDTO getCourseBase(TeachplanDTO dto, Long companyId) {
        CourseBaseDTO courseBase = courseBaseService.getCourseById(dto.getCourseId(), companyId);
        // 判断课程审核状态
        String auditStatus = courseBase.getAuditStatus();
        if (CourseAuditEnum.AUDIT_COMMIT_STATUS.equals(auditStatus) ||
                CourseAuditEnum.AUDIT_PASTED_STATUS.equals(auditStatus) ||
                CourseAuditEnum.AUDIT_PUBLISHED_STATUS.equals(auditStatus)
        ) {
            ExceptionCast.cast(ContentErrorCode.E_120015);
        }
        return courseBase;
    }

    //todo 获得父级节点数据
    private Teachplan getParentNode(Teachplan teachplan, CourseBaseDTO courseBase) {
        Long parentId = teachplan.getParentid();
        //todo 判断parentId是否存在,
        if (ObjectUtils.isEmpty(parentId)) {
            //todo 不存在是二级课程计划
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getCourseId, courseBase.getCourseBaseId());
            queryWrapper.eq(Teachplan::getParentid, TeachPlanEnum.FIRST_PARENTID_FLAG);
            Teachplan parentNode = this.getOne(queryWrapper);//查询二级课程计划的父级节点
            //判断二级是否存在
            if (ObjectUtils.isEmpty(parentNode)) {
                parentNode = new Teachplan(); //Teachplan创建对象
                //添加相关数据
                parentNode.setPname(courseBase.getName()); //添加课程名字
                parentNode.setCourseId(courseBase.getCourseBaseId());//添加关联的课程id
                parentNode.setParentid(new Long(TeachPlanEnum.FIRST_PARENTID_FLAG.toString()));//课程父级
                parentNode.setGrade(TeachPlanEnum.FIRST_LEVEL); //添加父级课程计划等级
                parentNode.setDescription(courseBase.getDescription());//添加课程计划
                parentNode.setOrderby(TeachPlanEnum.FIRST_LEVEL);//添加课程排序 因为是 创建所以说是1
                boolean save = this.save(parentNode); //创建完成添加课程计划
                //判断是否添加成功
                if (!save) {
                    //不成功直接抛异常
                    ExceptionCast.cast(ContentErrorCode.E_120407);
                }
            }
            //课程计划存在直接返回
            return parentNode;
        } else {
            //todo 存在是三级课程计划
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getId, parentId);
            Teachplan parentNode = this.getOne(queryWrapper); //查询三级的父节点是否存在
            if (ObjectUtils.isEmpty(teachplan)) {
                //todo 二级不存在,无法操作三级直接抛错
                ExceptionCast.cast(ContentErrorCode.E_120408);
            }
            return parentNode;
        }
    }

    /*
    todo 删除课程计划
       业务分析:
             1 判断是否需要开启事物
             2判断课程计划id和机构id是否正确
             3查询相关课程,
                判断课程等级
                        判断二级课程计划是否有,关联小结,有无法删除
                        判断三级课程计划是否有关联的媒资信息,有无发删除
     */
    @Override
    @Transactional
    public void removeTeachPlan(Long teachPlanId, Long companyId) {

        //todo 2判断课程计划id
        Teachplan teachPlan = this.getById(teachPlanId);
        if (ObjectUtils.isEmpty(teachPlan)) {
            ExceptionCast.cast(ContentErrorCode.E_120412);
        }
        //todo 2.1 机构id是否正确
        CourseBase courseBase = courseBaseService.getById(teachPlan.getCourseId());
        if (courseBase.getId().equals(companyId)) {
            ExceptionCast.cast(CommonErrorCode.E_110009);
        }
        //todo 3判断课程等级
        if (TeachPlanEnum.SECEND_LEVEL.equals(teachPlan.getGrade())) {
            //todo 相等说明是二级课程
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getParentid, teachPlan.getId());
            int count = this.count(queryWrapper);
            //todo 判断二级课程计划是否有,关联小结,有无法删除
            if (count > 0) {
                ExceptionCast.cast(ContentErrorCode.E_120409);
            }
        } else {
            //todo 不相等说明是三级课程
            LambdaQueryWrapper<TeachplanMedia> queryWrapper = new LambdaQueryWrapper<>();

            queryWrapper.eq(TeachplanMedia::getTeachplanId, teachPlan.getId());
            int count = teachplanMediaService.count(queryWrapper);
            if(count>0){
                ExceptionCast.cast(ContentErrorCode.E_120414);
            }
        }
        boolean b = this.removeById(teachPlanId);
        if(!b){
            ExceptionCast.cast(ContentErrorCode. E_120407);
        }
    }
/*
课程计划绑定媒资
 */
    @Override
    public TeachplanMediaDTO associateMedia(TeachplanMediaDTO dto, Long companyId) {
        //1.判断关键数据
        //     teachplanId、mediaId,companyId
        Long teachplanId = dto.getTeachplanId();
        Long mediaId = dto.getMediaId();
        if (ObjectUtils.isEmpty(teachplanId) ||
                ObjectUtils.isEmpty(mediaId) ||
                ObjectUtils.isEmpty(companyId)
        ) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }


        // 2.判断业务数据
        //    2.1   课程的计划
        //         判断课程计划是否存在：teachplanId、courseId
        //         判断是否是第三级
        //
        Teachplan teachplan = this.getById(teachplanId);

        if (ObjectUtils.isEmpty(teachplan)) {
            ExceptionCast.cast(ContentErrorCode.E_120402);
        }

        Integer grade = teachplan.getGrade();
        if (!(TeachPlanEnum.THIRD_LEVEL.equals(grade))) {
            ExceptionCast.cast(ContentErrorCode.E_120410);
        }



        //   2.2 课程基本信息
        //         判断是否存在或是否是同一家机构
        //         判断是否删除
        //         判断审核状态：未提交、审核未通过
        //         判断课程类型是否录播（课程基本信息中获得并判断）
        Long courseId = teachplan.getCourseId();

        // 判断是否存在或是否是同一家机构   判断是否删除
        CourseBaseDTO courseBaseDTO = courseBaseService.getCourseById(courseId, companyId);


        // 判断审核状态：未提交、审核未通过
        String auditStatus = courseBaseDTO.getAuditStatus();


        if (CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_COMMIT_STATUS.getCode().equals(auditStatus)
        ) {
            ExceptionCast.cast(ContentErrorCode.E_120015);
        }

        // 判断课程类型是否录播（课程基本信息中获得并判断）
        String teachmode = courseBaseDTO.getTeachmode();
        if (!(CourseModeEnum.COURSE_MODE_RECORD_STATUS.getCode().equals(teachmode))) {
            ExceptionCast.cast(ContentErrorCode.E_120416);
        }


        //     媒资信息
        //         判断媒资信息是否存在
        //         判断是否是同一家机构
        //         判断媒资审核状态
        RestResponse<MediaDTO> response = mediaApiAgent.getMediaById4s(mediaId);

        if (!(response.isSuccessful())) {
            ExceptionCast.castWithCodeAndDesc(response.getCode(),response.getMsg());
        }

        // 判断媒资信息是否存在
        MediaDTO mediaDTO = response.getResult();
        if (ObjectUtils.isEmpty(mediaDTO)) {
            ExceptionCast.cast(ContentErrorCode.E_120417);
        }

        // 判断是否是同一家机构
        if (!(ObjectUtils.nullSafeEquals(mediaDTO.getCompanyId(),companyId))) {
            ExceptionCast.cast(CommonErrorCode.E_403000);
        }

        String mediaAuditStatus = mediaDTO.getAuditStatus();
        // 判断媒资审核状态
        if (!(AuditEnum.AUDIT_PASTED_STATUS.getCode().equals(mediaAuditStatus))) {
            ExceptionCast.cast(ContentErrorCode.E_120418);
        }




        // 3.将dto数据转为po数据并保存到teachplanmedia表中
        //     添加或修改
        //         如果存在：
        //             对课程计划的媒资信息进行修改：mediaId，mediaFileName
        //         如果不存在：
        //             课程计划和媒资信息的添加

        LambdaQueryWrapper<TeachplanMedia> teachplanMediaQueryWrapper = new LambdaQueryWrapper<>();
        teachplanMediaQueryWrapper.eq(TeachplanMedia::getTeachplanId,teachplanId);
        teachplanMediaQueryWrapper.eq(TeachplanMedia::getCourseId,courseId);

        TeachplanMedia teachplanMedia = teachplanMediaService.getOne(teachplanMediaQueryWrapper);

        boolean result = false;

        if (ObjectUtils.isEmpty(teachplanMedia)) {
            // 添加操作
            teachplanMedia = new TeachplanMedia();

            teachplanMedia.setMediaId(mediaId);
            teachplanMedia.setTeachplanId(teachplanId);
            teachplanMedia.setCourseId(courseId);
            teachplanMedia.setMediaFilename(mediaDTO.getFilename());

            result = teachplanMediaService.save(teachplanMedia);



        } else {
            // 修改操作: 改为UpdateWrapper
            teachplanMedia.setMediaFilename(mediaDTO.getFilename());
            teachplanMedia.setMediaId(mediaId);

            result = teachplanMediaService.updateById(teachplanMedia);

        }

        // 4.判断保存数据的结果并返回
        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120411);
        }
        TeachplanMediaDTO resultDTO = TeachplanMediaConvert.INSTANCE.entity2dto(teachplanMedia);
        return resultDTO;
    }

}
